﻿using System;
using System.Web.Security;
using System.Web;
using System.Web.Configuration;
using System.Security.Principal;
using System.Security.Permissions;
using System.Globalization;
using System.Runtime.Serialization;
using System.Collections;
using System.Collections.Specialized;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Configuration.Provider;
using System.Configuration;
using System.Web.DataAccess;
using System.Web.Management;
using System.Web.Util;

namespace BLL
{
    public class MembershipBase
    {

        /// <summary>
        /// The name of the application using the custom membership provider.
        /// </summary>
        public bool EnablePasswordRetrieval { get { return _EnablePasswordRetrieval; } }

        /// <summary>
        /// Indicates whether the membership provider is configured to allow users to reset their passwords.
        /// </summary>
        public bool EnablePasswordReset { get { return _EnablePasswordReset; } }

        /// <summary>
        /// Gets a value indicating whether the membership provider is configured to require the user to answer a password question for password reset and retrieval.
        /// </summary>
        public bool RequiresQuestionAndAnswer { get { return _RequiresQuestionAndAnswer; } }

        /// <summary>
        /// Gets a value indicating whether the membership provider is configured to require a unique e-mail address for each user name.
        /// </summary>
        public bool RequiresUniqueEmail { get { return _RequiresUniqueEmail; } }

        /// <summary>
        /// Gets a value indicating the format for storing passwords in the membership data store.
        /// </summary>
        public Model.Enum.MembershipPasswordFormat PasswordFormat { get { return _PasswordFormat; } }

        /// <summary>
        /// Gets the number of invalid password or password-answer attempts allowed before the membership user is locked out.
        /// </summary>
        public int MaxInvalidPasswordAttempts { get { return _MaxInvalidPasswordAttempts; } }

        /// <summary>
        /// Gets the number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out.
        /// </summary>
        public int PasswordAttemptWindow { get { return _PasswordAttemptWindow; } }

        /// <summary>
        /// Gets the minimum length required for a password.
        /// </summary>
        public int MinRequiredPasswordLength
        {
            get { return _MinRequiredPasswordLength; }
        }

        /// <summary>
        /// Gets the minimum number of special characters that must be present in a valid password.
        /// </summary>
        public int MinRequiredNonAlphanumericCharacters
        {
            get { return _MinRequiredNonalphanumericCharacters; }
        }

        /// <summary>
        /// Gets the regular expression used to evaluate a password.
        /// </summary>
        public string PasswordStrengthRegularExpression
        {
            get { return _PasswordStrengthRegularExpression; }
        }

        private bool _EnablePasswordRetrieval;
        private bool _EnablePasswordReset;
        private bool _RequiresQuestionAndAnswer; 
        private bool _RequiresUniqueEmail;
        private int _MaxInvalidPasswordAttempts; 
        private int _PasswordAttemptWindow;
        private int _MinRequiredPasswordLength;
        private int _MinRequiredNonalphanumericCharacters;
        private string _PasswordStrengthRegularExpression;
        private Model.Enum.MembershipPasswordFormat _PasswordFormat;

        private const int PASSWORD_SIZE = 14;

        public MembershipBase()
        {
            _EnablePasswordRetrieval = false;
            _EnablePasswordReset = true;
            _RequiresQuestionAndAnswer = true;
            _RequiresUniqueEmail = true;
            _MaxInvalidPasswordAttempts = 5;
            _PasswordAttemptWindow = 10;
            _MinRequiredPasswordLength = 7;
            _MinRequiredNonalphanumericCharacters = 0;

            _PasswordStrengthRegularExpression = "";

            string strTemp = "Hashed";

            switch (strTemp)
            {
                case "Clear":
                    _PasswordFormat = Model.Enum.MembershipPasswordFormat.Clear;
                    break;
                case "Encrypted":
                    _PasswordFormat = Model.Enum.MembershipPasswordFormat.Encrypted;
                    break;
                case "Hashed":
                    _PasswordFormat = Model.Enum.MembershipPasswordFormat.Hashed;
                    break;
                default:
                    throw new ProviderException(SR.GetString(SR.Provider_bad_password_format));
            }
        }

        /// <summary>
        /// Adds a new membership user to the data source.
        /// </summary> 
        /// <returns></returns>
        public Model.MembershipBase CreateUser(Model.MembershipBase _userObj, out Model.Enum.MembershipCreateStatus status)
        {
            string username = _userObj.UserName;
            string password = _userObj.Password;
            string email = _userObj.Email;
            string passwordQuestion = _userObj.PasswordQuestion;
            string passwordAnswer = _userObj.PasswordAnswer;
            bool isApproved = _userObj.IsApproved;
            object providerUserKey = _userObj.UserId;

            if (!SecUtility.ValidateParameter(ref password, true, true, false, 128))
            {
                status = Model.Enum.MembershipCreateStatus.InvalidPassword;
                return null;
            }

            string salt = GenerateSalt();
            string pass = FormsAuthentication.HashPasswordForStoringInConfigFile(password.Trim(), "md5"); //EncodePassword(password, 1, salt);
            if (pass.Length > 128)
            {
                status = Model.Enum.MembershipCreateStatus.InvalidPassword;
                return null;
            }

            string encodedPasswordAnswer;
            if (passwordAnswer != null)
            {
                passwordAnswer = passwordAnswer.Trim();
            }

            if (!string.IsNullOrEmpty(passwordAnswer))
            {
                if (passwordAnswer.Length > 128)
                {
                    status = Model.Enum.MembershipCreateStatus.InvalidAnswer;
                    return null;
                }
                encodedPasswordAnswer = EncodePassword(passwordAnswer.ToLower(CultureInfo.InvariantCulture), (int)_PasswordFormat, salt);
            }
            else
                encodedPasswordAnswer = passwordAnswer;

            #region 密码提示问题
            //if (!SecUtility.ValidateParameter(ref encodedPasswordAnswer, RequiresQuestionAndAnswer, true, false, 128))
            //{
            //    status = Model.Enum.MembershipCreateStatus.InvalidAnswer;
            //    return null;
            //}
            #endregion

            if (!SecUtility.ValidateParameter(ref username, true, true, true, 256))
            {
                status = Model.Enum.MembershipCreateStatus.InvalidUserName;
                return null;
            }

            if (!SecUtility.ValidateParameter(ref email,
                                               RequiresUniqueEmail,
                                               RequiresUniqueEmail,
                                               false,
                                               256))
            {
                status = Model.Enum.MembershipCreateStatus.InvalidEmail;
                return null;
            }

            #region 密码提示问题
            //if (!SecUtility.ValidateParameter(ref passwordQuestion, RequiresQuestionAndAnswer, true, false, 256))
            //{
            //    status = Model.Enum.MembershipCreateStatus.InvalidQuestion;
            //    return null;
            //} 
            #endregion

            if (password.Length < MinRequiredPasswordLength)
            {
                status = Model.Enum.MembershipCreateStatus.InvalidPassword;
                return null;
            }

            #region 检测密码中是否含有特殊字符
            //int count = 0;

            //for (int i = 0; i < password.Length; i++)
            //{
            //    if (!char.IsLetterOrDigit(password, i))
            //    {
            //        count++;
            //    }
            //}

            //if (count < MinRequiredNonAlphanumericCharacters)
            //{
            //    status = MembershipCreateStatus.InvalidPassword;
            //    return null;
            //}
            #endregion

            if (PasswordStrengthRegularExpression.Length > 0)
            {
                if (!Regex.IsMatch(password, PasswordStrengthRegularExpression))
                {
                    status = Model.Enum.MembershipCreateStatus.InvalidPassword;
                    return null;
                }
            }

            _userObj.Password = pass;
            _userObj.PasswordSalt = salt;
            _userObj.PasswordFormat = (int)_PasswordFormat;
            _userObj.PasswordAnswer = encodedPasswordAnswer;

            string result = (new SQLServerDAL.MembershipBase()).CreateUser(_userObj, _RequiresUniqueEmail);

            if (result.Split('|')[0] == "succeeded")
            {
                status = Model.Enum.MembershipCreateStatus.Success;
                _userObj.UserId = result.Split('|')[1];
            }
            else
            {
                int iStatus = 1;
                if (Common.Utils.IsNumber(result))
                    iStatus = int.Parse(result);
                else
                    iStatus = 11;

                status = (Model.Enum.MembershipCreateStatus)iStatus;
            }

            return _userObj;
        }

        /// <summary>
        /// Adds a new membership user to the data source.
        /// </summary> 
        /// <returns></returns>
        public Model.MembershipBase UpdateUser(Model.MembershipBase _userObj, out Model.Enum.MembershipCreateStatus status)
        {
            string username = _userObj.UserName; 
            string email = _userObj.Email; 
            bool isApproved = _userObj.IsApproved;
            object providerUserKey = _userObj.UserId;
    
            if (!SecUtility.ValidateParameter(ref username, true, true, true, 256))
            {
                status = Model.Enum.MembershipCreateStatus.InvalidUserName;
                return null;
            }

            if (!SecUtility.ValidateParameter(ref email,
                                               RequiresUniqueEmail,
                                               RequiresUniqueEmail,
                                               false,
                                               256))
            {
                status = Model.Enum.MembershipCreateStatus.InvalidEmail;
                return null;
            } 

            string result = (new SQLServerDAL.MembershipBase()).UpdateUser(_userObj, _RequiresUniqueEmail);

            if (result.Split('|')[0] == "succeeded")
            {
                status = Model.Enum.MembershipCreateStatus.Success;
                _userObj.UserId = result.Split('|')[1];
            }
            else
            {
                int iStatus = 1;
                if (Common.Utils.IsNumber(result))
                    iStatus = int.Parse(result);
                else
                    iStatus = 11;

                status = (Model.Enum.MembershipCreateStatus)iStatus;
            }

            return _userObj;
        }
 
        /// <summary>
        /// 会员信息实体
        /// </summary>
        /// <param name="productId"></param>
        /// <returns></returns>
        public static Model.MembershipBase GetObjById(string userId)
        {
            return (new SQLServerDAL.MembershipBase()).GetObjById(userId);
        }

        /// <summary>
        /// 会员产品信息实体
        /// </summary>
        /// <param name="productId"></param>
        /// <returns></returns>
        public static Model.MembershipBase GetObjByUsername(string userName)
        {
            return (new SQLServerDAL.MembershipBase()).GetObjByUsername(userName);
        }

        /// <summary>
        /// 修改密码
        /// </summary>
        /// <param name="productId"></param>
        /// <returns></returns>
        public void ChangePassword(string username, string password, out Model.Enum.MembershipCreateStatus status)
        {
            string salt = GenerateSalt();
            string pass = FormsAuthentication.HashPasswordForStoringInConfigFile(password.Trim(), "md5"); // EncodePassword(password, (int)_PasswordFormat, salt);
            if (pass.Length > 128)
            {
                status = Model.Enum.MembershipCreateStatus.InvalidPassword; 
            }
 
            int iStatus = (new SQLServerDAL.MembershipBase()).UpdatePassword(username, pass, (int)_PasswordFormat, _PasswordAttemptWindow, _MaxInvalidPasswordAttempts,  salt);

            status = (Model.Enum.MembershipCreateStatus)iStatus;
        }

        /// <summary>
        /// Verifies that the specified user name and password exist in the data source.
        /// </summary>
        /// <param name="username">The name of the user to validate.</param>
        /// <param name="password">The password for the specified user.</param>
        /// <returns></returns>
        public bool ValidateUser(string username, string password)
        {
            if (SecUtility.ValidateParameter(ref username, true, true, true, 256) &&
                    SecUtility.ValidateParameter(ref password, true, true, false, 128))
            {
                string salt;
                int passwordFormat;

                string passwdFromDB;
                int status;
                int failedPasswordAttemptCount;
                int failedPasswordAnswerAttemptCount;
                bool isPasswordCorrect;
                bool isApproved;
                bool updateLastLoginActivityDate = true;
                bool failIfNotApproved = true;
                DateTime lastLoginDate, lastActivityDate;

                SQLServerDAL.MembershipBase _obj = new SQLServerDAL.MembershipBase();

                _obj.GetPasswordWithFormat(username, updateLastLoginActivityDate, out status, out passwdFromDB, out passwordFormat, out salt, out failedPasswordAttemptCount,
                      out failedPasswordAnswerAttemptCount, out isApproved, out lastLoginDate, out lastActivityDate);
  
                if (status != 0)
                    return false;

                if (!isApproved && failIfNotApproved)
                    return false;

                string encodedPasswd = FormsAuthentication.HashPasswordForStoringInConfigFile(password.Trim(), "md5");  //EncodePassword(password, passwordFormat, salt);

                isPasswordCorrect = passwdFromDB.Equals(encodedPasswd);

                if (isPasswordCorrect && failedPasswordAttemptCount == 0 && failedPasswordAnswerAttemptCount == 0)
                {
                    _obj.UpdateMemberInfo(username, isPasswordCorrect, updateLastLoginActivityDate, failIfNotApproved, lastLoginDate, lastActivityDate);
                    return true;
                }
                else
                    return false;
            }
            else
            { 
                return false;
            }
        }

        /// <summary>
        /// 删除会员帐号
        /// </summary> 
        /// <returns></returns>
        public static bool DeleteUser(string username)
        {
            return (new SQLServerDAL.MembershipBase()).DeleteUser(username);
        }

        #region 验证唯一
        /// <summary>
        /// 验证 E-Mail 是否唯一, true 已存在, false 唯一
        /// </summary>
        public static bool ValidateEmailExist(string email)
        {
            return (new SQLServerDAL.MembershipBase()).ValidateEmailExist(email);
        }
        /// <summary>
        /// 验证 Username 是否唯一, true 已存在, false 唯一
        /// </summary>
        public static bool ValidateUsernameExist(string username)
        {
            return (new SQLServerDAL.MembershipBase()).ValidateUsernameExist(username);
        }
        #endregion

        public string _EncodePassword(string pass, int passwordFormat, string salt)
        { 
            if (passwordFormat == 0) // MembershipPasswordFormat.Clear
                return pass;

            byte[] bIn = Encoding.Unicode.GetBytes(pass);
            byte[] bSalt = Convert.FromBase64String(salt);
            byte[] bAll = new byte[bSalt.Length + bIn.Length];
            byte[] bRet = null;

            Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
            Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);

            // MembershipPasswordFormat.Hashed
            HashAlgorithm s = HashAlgorithm.Create(Membership.HashAlgorithmType);
            bRet = s.ComputeHash(bAll);

            return Convert.ToBase64String(bRet);
        }

        #region 内部方法
        internal string EncodePassword(string pass, int passwordFormat, string salt)
        {
            if (passwordFormat == 0) // MembershipPasswordFormat.Clear
                return pass;

            byte[] bIn = Encoding.Unicode.GetBytes(pass);
            byte[] bSalt = Convert.FromBase64String(salt);
            byte[] bAll = new byte[bSalt.Length + bIn.Length];
            byte[] bRet = null;

            Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
            Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);

            // MembershipPasswordFormat.Hashed
            HashAlgorithm s = HashAlgorithm.Create(Membership.HashAlgorithmType);
            
            bRet = s.ComputeHash(bAll);

            return Convert.ToBase64String(bRet);
        }
        /////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        private string GetExceptionText(int status)
        {
            string key;
            switch (status)
            {
                case 0:
                    return String.Empty;
                case 1:
                    key = SR.Membership_UserNotFound;
                    break;
                case 2:
                    key = SR.Membership_WrongPassword;
                    break;
                case 3:
                    key = SR.Membership_WrongAnswer;
                    break;
                case 4:
                    key = SR.Membership_InvalidPassword;
                    break;
                case 5:
                    key = SR.Membership_InvalidQuestion;
                    break;
                case 6:
                    key = SR.Membership_InvalidAnswer;
                    break;
                case 7:
                    key = SR.Membership_InvalidEmail;
                    break;
                case 99:
                    key = SR.Membership_AccountLockOut;
                    break;
                default:
                    key = SR.Provider_Error;
                    break;
            }
            return SR.GetString(key);
        }

        private bool IsStatusDueToBadPassword(int status)
        {
            return (status >= 2 && status <= 6 || status == 99);
        }

        private DateTime RoundToSeconds(DateTime dt)
        {
            return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second);
        }
        internal string GenerateSalt()
        {
            byte[] buf = new byte[16];
            (new RNGCryptoServiceProvider()).GetBytes(buf);
            return Convert.ToBase64String(buf);
        }
        #endregion
    }
}
